===================
Command-line client
===================

SYNOPSIS
========

:command:`sievemgr` [:option:`server`] [:option:`command`]
[:option:`argument` ...]

:command:`sievemgr` :option:`-e expression` [...] [:option:`server`]

:command:`sievemgr` :option:`-s file` [:option:`server`]

:command:`sievemgr` :option:`-h`

:command:`sievemgr` :option:`-V`


DESCRIPTION
===========

:command:`sievemgr` is a command-line client for uploading, downloading,
and managing Sieve scripts using the ManageSieve protocol.

By default, a shell is entered and commands are read from standard input;
the shell supports tab-completion.

However, commands can also be given on the command line, as `expression`
with :option:`-e`, or read from a `file` given with :option:`-s`.
If a `command` is given on the command line or as `expression` with
:option:`-e`, it is executed and the shell is not entered. If a `file`
is given with :option:`-s`, expressions are read from that `file`,
rather than from standard input.

.. only:: html

    The `server` defaults to the :confvar:`host`
    set in :doc:`sieve.cf <sieve.cf>` or :file:`localhost`.

.. only:: man

    The `server` defaults to the :confvar:`host`
    set in :manpage:`sieve.cf(5)` or :file:`localhost`.


OPERANDS
========

.. option:: server

    URL of the form
    :samp:`[{scheme}://][{login}[:{passwd}]@]{host}[:{port}][/{owner}]`.

    `scheme`
        defaults to "sieve" (and no other scheme is supported).

    `login`
        .. only:: html

            defaults to the :confvar:`login` set for `host` in
            :doc:`sieve.cf <sieve.cf>` or :file:`.netrc`, or, if
            no login is set, the current user.

        .. only:: man

            defaults to the :confvar:`login` set for `host` in
            :manpage:`sieve.cf(5)` or :file:`.netrc`, or,
            if no login is set, the current user.

    `passwd`
        is prompted for by default (see LOGIN_ for other options).

    `port`
        defaults to 4190 (the standard port for ManageSieve).

    `owner`
        defaults to `login`.

    .. danger::

        Other users can see passwords given on the command line.

.. option:: command

    Command to run (see COMMANDS_ below).

.. option:: argument

    Argument to that command.


OPTIONS
=======

.. option:: -C

    Do *not* overwrite files.

.. option:: -N file

    Use `file` as :file:`.netrc` file.

.. option:: -V

    Show version.

.. option:: -c file

    Read configuration from `file`.

.. option:: -d

    Enable debugging mode.

.. option:: -e expression

    Execute `expression` on the `server`.

.. option:: -f

    Overwrite and remove files without confirmation.

.. option:: -h

    Show help.

.. option:: -i

    Confirm removing or overwriting files.

.. option:: -o key=value

    .. only:: man

        Set configuration `key` to `value`.
        See :manpage:`sieve.cf(5)` for details.

    .. only:: html

        Set configuration `key` to `value`.
        See :doc:`sieve.cf` for details.

.. option:: -q

    Be quieter.

.. option:: -s file

    Execute expressions read from `file` on the `server`.

.. option:: -v

    Be more verbose.

:option:`-e`, :option:`-q`, and :option:`-v` can be given multiple times.


COMMANDS
========

.. sievecmd:: about

    Show information about SieveManager.

.. sievecmd:: activate script

    Marks `script` as the active script.
    This is the script that the mail server will run for incoming mail.
    Only one script can be active at a time.

.. sievecmd:: caps

    Show the server's capabilities (output is valid YAML_).

.. sievecmd:: cat [script ...]

    Print the given scripts to standard output.

.. sievecmd:: cd [localdir]

    Change to local working directory to `localdir`.
    `localdir` defaults to the current user's home directory.

.. sievecmd:: cert

    Show the server's TLS certificate (output is valid YAML_).

.. sievecmd:: cmp script1 [...] scriptN

    Compare scripts.

.. sievecmd:: cp [-f|-i] source target

    Download `source` and re-upload it as `target`.

    .. rubric:: Options:

    -f  Overwrite `target` without confirmation.
    -i  Ask for confirmation before overwriting `target`.

.. sievecmd:: deactivate

    Deactivate the active script

.. sievecmd:: diff [-C n|-U n|-c|-u] [-b] script1 script2

    Show how `script1` and `script2` differ.

    .. rubric:: Options:

    -C n    Show `n` lines of copied context.
    -U n    Show `n` lines of unified context.
    -b      Ignore whitespace that precedes a linefeed.
    -c      Show three lines of copied context.
    -u      Show three lines of copied context.

.. sievecmd:: echo word [...]

    Print `word` to standard output.

.. sievecmd:: ed [-a] script [...]

    Edit `script` with a line editor.

    .. rubric:: Options:

    -a      Edit the active script.

.. sievecmd:: exit

    Log out and exit.

.. sievecmd:: get [-f|-i] [-a] [-o file] [script ...]

    Download scripts.

    .. rubric:: Options:

    -a       Download the active script only.
    -f       Overwrite files without confirmation.
    -i       Ask for confirmation before overwriting a file.
    -o file  Save `script` to `file`. Can be used with one `script` only.

.. sievecmd:: help, ? [command]

    Show help for `command`.
    List commands if `command` is omitted.

    For example:

    .. code:: none

        sieve://user@imap.foo.example> ?ls
        ls [script ...] - list scripts

.. sievecmd:: ls [-1al] [script ...]

    List the given scripts or, if no `script` is given, all scripts.
    The active script is marked with an asterisk ("\*").

    .. rubric:: Options:

    -1  List one script per line.
        Implied if standard input is not a terminal.

    -a  List the active script only.

    -l  List one flag-script name pair per line, separated by whitespace,
        where the flag is one of:

        :literal:`a`
            Script is active.

        :literal:`e`
            End-of-transmission mark.
            *Not* followed by a script name.

        If neither flag applies, a dash ("-") is printed instead.

    The active script is *not* marked with an asterisk ("\*") if
    ``-1``, ``-a``, or ``-l`` is given.

    .. rubric:: Examples:

    .. code:: none

        sieve://user@imap.foo.example> ls
        bar.sieve foo.sieve*

    .. code:: none

        sieve://user@imap.foo.example> ls -l
        - bar.sieve
        a foo.sieve
        e

.. sievecmd:: more [-aceis] [-p command] [script ...]

    Display scripts page-by-page.

    .. rubric:: Options:

    -a          Display the active script only.
    -c          Clear the screen instead of scrolling.
    -e          Exit immediately after writing the last line of `script`.
    -i          Ignore case in pattern matching.
    -p command  Execute `command` when opening or switching to a file.
    -s          Treat consecutive empty lines as a single empty line.

.. sievecmd:: mv [-f|-i] source target

    Rename `source` to `target`.

    .. rubric:: Options:

    -f  Replace `target` without confirmation.
    -i  Ask for confirmation before replacing `target`.

.. sievecmd:: put [-f|-i] [-a] [-o name] [localscript ...]

    Upload scripts.

    -a       Replace the active script or, if no script is active,
             activate the script after uploading.

    -f       Replace scripts without confirmation.

    -i       Ask for confirmation before replacing a script.

    -o name  Upload `localscript` as `name`.
             Can be used with one script only.

.. sievecmd:: python

    Enter a Python read-evaluate-print loop,
    with the current connection as the 'root' object.

.. sievecmd:: rm [-f|-i] [script ...]

    Remove scripts.

    -f  Remove scripts without confirmation.
    -i  Ask for confirmation before removing a script.

.. sievecmd:: sh, ! [command] [argument ...]

    Run the system `command` with the given arguments.
    If `command` is omitted, enter a system shell.

    .. rubric:: Examples:

    .. code:: none

        sieve://user@imap.foo.example> cd sieve
        sieve://user@imap.foo.example> !pwd
        /home/user/sieve
        sieve://user@imap.foo.example> !ls
        foo.sieve bar.sieve
        sieve://user@imap.foo.example> put foo.sieve

    .. code:: none

        sieve://user@imap.foo.example> !
        bash-2.0$

.. sievecmd:: su user

    Manage the scripts of `user`.
    Requires elevated privileges.

.. sievecmd:: vi [-a] script [...]

    Edit `script` with a visual editor.

    .. rubric:: Options:

    -a  Edit the active script only.

.. sievecmd:: xargs command [argument ...]

    Call `command` with the given arguments and each line from
    standard input as additional argument up to, but excluding,
    the first empty line or the end-of-file mark.


PATTERNS
========

If :samp:`*`, :samp:`?`, or :samp:`[{chars}]` occur in an expression given
with :option:`-e`, a script given with :option:`-s`, or a command line
read from standard input, they are expanded to local or remote filenames
in the same way as a they would be expanded by the shell. If a command
operates on local scripts, patterns are expanded to matching filenames on
the local system; if a command operates on remote scripts, patterns are
expanded to matching filenames on the server.

For example,

.. code:: none

    sieve://user@imap.foo.example> put *.sieve

uploads every local file that matches :samp:`*.sieve`.

By contrast,

.. code:: none

    sieve://user@imap.foo.example> rm foo.*

deletes every file that matches :samp:`foo.*` from :file:`imap.foo.example`.


SCRIPTING
=========

Operations can be scripted by giving a :option:`command` as argument,
by redirecting standard input, or by using :option:`-e` or :option:`-s`.


Basics
------

Scripts abort if an error occurs, so errors must be prevented.

Confirmation is always prompted for on the controlling terminal,
regardless of input/output redirection. If there is no controlling
terminal, operations that require confirmation trigger an error.

Comments start with a '#' and are ignored.


Comparing scripts
-----------------

:sievecmd:`cmp` can be used to compare remote scripts.

.. code:: bash

    $ if sievemgr user@imap.foo.example cmp -s foo.sieve bar.sieve
    > then echo 'foo.sieve and bar.sieve are equal'
    > else echo 'foo.sieve and bar.sieve differ'
    > fi

.. code:: bash

    $ case $(sievemgr user@imap.foo.example cmp foo.sieve bar.sieve) in
    > (*equal)   echo 'foo.sieve and bar.sieve are equal' ;;
    > (*differs) echo 'foo.sieve and bar.sieve differ' ;;
    > esac


Listing scripts
---------------

:sievecmd:`ls` prints one script name per line if standard input
is not a terminal. Sieve script names must not contain linefeeds,
so :sievecmd:`ls` can safely be used in scripts.

.. code:: bash

    $ sievemgr -e'ls -a' -e'deactivate' user@imap.foo.example |
    > sievemgr user@imap.foo.example xargs rm -f

.. code:: bash

    $ mkfifo pipe
    $ sievemgr user@imap.foo.example ls -l >pipe & pid=$!
    $ nscripts=0
    $ while read -r flag script && [ "$script" ]
    > do
    >     eval "script_${nscripts}"='$script'
    >     nscripts=$((nscripts + 1))
    > done <pipe
    $ wait $pid


Word expansion
--------------

Expressions in scripts are subject to word splitting and pattern expansion,
so arguments containing whitespace or patterns must be quoted or escaped.

For example,

.. code:: bash

    $ sievemgr -e'get foo bar *.sieve' user@imap.foo.example

downloads the files :file:`foo`, :file:`bar`, and every file
*matching* the pattern :samp:`*.sieve`.

By contrast,

.. code:: bash

    $ sievemgr -e'get "foo bar" "*.sieve"' user@imap.foo.example

downloads the file :file:`foo bar` and the *file* :file:`*.sieve`.

Arguments can also be protected from expansion by using
:sievecmd:`xargs`. For example,

.. code:: bash

    $ sievemgr user@imap.foo.example <<EOF
    > xargs get
    > foo bar
    > *.sieve
    > EOF

also downloads the files :file:`foo bar` and :file:`*.sieve`.


Persistent connections
----------------------

A connection is opened each time :command:`sievemgr` is called. So
if multiple message are going to be exchanged between the client and
the server, it is more efficient to run :command:`sievemgr` in the
background and send and receive messages through pipes than to
call :command:`sievemgr` for each exchange.

Create one pipe for sending commands and one for receiving responses:

.. code:: bash

    $ mkfifo -m0600 xfer recv

Start :command:`sievemgr` and redirect its input and output to these pipes:

.. code:: bash

    $ sievemgr user@imap.foo.example <xfer >recv & pid=$!

Open the pipes in the shell:

.. code:: bash

    $ exec 3>xfer
    $ exec 4<recv

Commands can now be sent by writing them to file descriptor (FD) 3:

.. code:: bash

    $ echo ls -l >&3

And responses can be read from FD 4:

.. code-block:: bash
    :emphasize-lines: 6

    $ nscripts=0
    $ while read -r flag script && [ "$script" ]
    > do
    >     eval "script_${nscripts}"='$script'
    >     nscripts=$((nscripts + 1))
    > done <&4

However, be careful to avoid deadlocks:

.. code-block:: bash
    :caption: Deadlock
    :emphasize-lines: 3

    echo ls -a >&3
    # read will wait forever if there is no active script.
    active="$(read <&4)"

.. code-block:: bash
    :caption: Deadlock
    :emphasize-lines: 4

    echo ls -l >&3
    nscripts=0
    # The loop reads past the output of ls -l and then waits forever.
    while read -r _ script
    do
        eval "script_${nscripts}"='$script'
        nscripts=$((nscripts + 1))
    done <&4

The output of :command:`read` must be checked
for an end-of-transmission mark:

======================  ==================================
Command                 End-of-transmission mark
======================  ==================================
:sievecmd:`caps`        ``...``
:sievecmd:`cert`        ``...``
:sievecmd:`cmp`         Ends with ``equal`` or ``differs``
:sievecmd:`diff`        Add a mark with ``echo``
:sievecmd:`ls -l <ls>`  ``e`` in first word
======================  ==================================

Also be careful to avoid races:

.. code-block:: bash
    :caption: Race condition
    :emphasize-lines: 3

    echo get foo.sieve >&3
    # patch will, likely, run BEFORE get has downloaded foo.sieve.
    patch foo.sieve <patchfile

Use :sievecmd:`echo` to wait for previously sent commands to finish:

.. code-block:: bash
    :emphasize-lines: 3, 5-7

    $ cat <<EOF >&3
    > get foo.sieve
    > echo fin
    > EOF
    $ while read -r line && [ "$line" != fin ]
    > do :
    > done <&4
    $ patch foo.sieve <patchfile

:command:`read` blocks, so this is *not* a busy wait.

Exit by sending :sievecmd:`exit`:

.. code:: bash

    $ echo exit >&3

But abort by:

.. code:: bash

    $ kill "$pid"

Exiting by writing :sievecmd:`exit` to FD 3, instead of by sending a
:literal:`TERM` with :command:`kill`, makes sure that :command:`sievemgr`
exits only after it has finished executing previously sent commands.
Conversely, aborting with :command:`kill` makes sure that
:command:`sievemgr` exits right away.


Example
-------

.. literalinclude:: ../examples/sievepatch
    :language: bash


LOGIN
=====

The login can be automated by reading passwords from :file:`.netrc` or
the standard output of a command or by using TLS client authentication.

.netrc
------

The :file:`.netrc` file is a traditional facility to automate logins.

See the *GNU network utilities manual* (chap. `11.7 <netrc_>`_) for details.


Password managers
-----------------

Set :confvar:`getpass` to a :samp:`{command}` to read the password
from the standard output of that *command*.

For example,

.. code:: bash

    $ sievemgr -ogetpass='pass $host/$login' user@imap.foo.example

queries :command:`pass` for the password for :file:`imap.foo.example/user`.
:samp:`$host` and :samp:`$login`  are expanded to the given `host` and the
login for that `host` respectively.

.. only:: html

    See :doc:`sieve.cf` for details and more examples.

.. only:: man

    See :manpage:`sieve.cf(5)` for details and more examples.


TLS client authentication
-------------------------

There are two types of TLS client authentication. Sending a TLS client
certificate may be required for some other form of authentication, which
is also required, to be permitted or the user may be authenticated by
sending a certificate.

Both types require sending a TLS certificate. Set :confvar:`cert` to a
:file:`{file}` to load a TLS key/certificate pair from that :file:`{file}`.

For example,

.. code:: bash

    $ sievemgr -ocert=~/certs/client.pem user@imap.foo.example

loads the TLS key/certificate pair from :file:`~/certs/client.pem`.

To authenticate by sending a TLS client certificate,
additionally set :confvar:`authmechs` to ``external``.

.. only:: html

    See :doc:`sieve.cf` for details.

.. only:: man

    See :manpage:`sieve.cf(5)` for details.


EXIT STATUS
===========

0
    Success

1
    Failure

2
    Usage error


ENVIRONMENT
===========

.. envvar:: COLUMNS

    Terminal height.

.. envvar:: EDITOR

    Editor called by :sievecmd:`ed` (default: :command:`ed`).

.. envvar:: HOME

    Home directory of the current user.

.. envvar:: LANG, LC_ALL, LC_CTYPE

    I/O encoding.
    :envvar:`LC_ALL` takes precedence over :envvar:`LC_CTYPE` and
    :envvar:`LC_CTYPE` takes precedence over :envvar:`LANG`.

.. envvar:: LINES

    Terminal width.

.. envvar:: LOGNAME

    Login name of the current user.

.. envvar:: PAGER

    Pager called by :sievecmd:`more` (default: :command:`more`).

.. envvar:: NETRC

    Filename of the :file:`.netrc` file
    (default: :file:`{$HOME}/.netrc`).

.. envvar:: VISUAL

    Editor called by :sievecmd:`vi` (default: :command:`vi`).

.. envvar:: XDG_CONFIG_HOME

    X Desktop Group base configuration directory
    (default: :file:`{$HOME}/.config`).


FILES
=====

.. index:: pair: $XDG_CONFIG_HOME/sieve/config; file

:file:`{$XDG_CONFIG_HOME}/sieve/config`

.. index:: pair: $HOME/.sieve/config; file

:file:`{$HOME}/.sieve/config`

.. index:: pair: $HOME/.sieve.cf; file

:file:`{$HOME}/.sieve.cf`

.. index:: pair: /etc/sieve/config; file

:file:`/etc/sieve/config`

.. index:: pair: /etc/sieve.cf; file

:file:`/etc/sieve.cf`
    The configuration is read from the first file that is found.

    .. only:: man

        See :manpage:`sieve.cf(5)` for details.

    .. only:: html

        See :doc:`Configuration <sieve.cf>` for details.

.. index:: pair: .netrc; file

:file:`.netrc`
    Login information.


STANDARDS
=========

:RFC:`2195` (CRAM-MD5)

:RFC:`2244` (ACAP)

:RFC:`2782` (SRV records)

:RFC:`4013` (SASLprep)

:RFC:`4422` (SASL)

:RFC:`4616` (PLAIN)

:RFC:`5228` (Sieve) 

:RFC:`5802` (SCRAM)

:RFC:`5804` (ManageSieve)

:RFC:`5019` (Lightweight OCSP)

:RFC:`6960` (OCSP)

:RFC:`7677` (SCRAM-SHA-256 and SCRAM-SHA-256-PLUS)


CAVEATS
=======

Credentials are stored in memory. However, because page-locking is
unfeasible in Python, they may be swapped out to the disk.

Sieve scripts must be encoded in UTF-8.


BUGS
====

:file:`.netrc` records without a :confvar:`password` token wrongly trigger a
parse error in Python up to version 3.9. To avoid valid :file:`.netrc` files
triggering errors, :file:`.netrc` parse errors are turned into warnings
when SieveManager is executed by Python version 3.9 or below.


EXAMPLES
========

Upload :file:`script.sieve` to :file:`imap.foo.example` and activate it:

.. code:: none

    $ sievemgr user@imap.foo.example
    sieve://user@imap.foo.example> put script.sieve
    sieve://user@imap.foo.example> activate script.sieve
    sieve://user@imap.foo.example> exit

Edit the active script on :file:`imap.foo.example`:

.. code:: bash

    $ sievemgr user@imap.foo.example vi -a

Reading commands from standard input:

.. code:: bash

    $ sievemgr user@imap.foo.example <<EOF
    > put script.sieve
    > activate script.sieve
    > EOF

Download all scripts from :file:`imap.foo.example`:

.. code:: bash

    $ sievemgr -e'get *' user@imap.foo.example


.. only:: man

    SEE ALSO
    ========

    :manpage:`sieve.cf(5)`

